package net.luohuasheng.bee.proxy.core.utils.convert;

import net.luohuasheng.bee.proxy.core.enums.PrimitiveType;
import net.luohuasheng.bee.proxy.core.exception.ConventException;
import net.luohuasheng.bee.proxy.core.utils.classloader.ClassLoaderUtils;
import net.luohuasheng.bee.proxy.core.utils.classloader.dto.ClassMethodDto;
import org.apache.commons.lang3.time.DateUtils;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.util.Date;
import java.util.Objects;

/**
 * @author luohuasheng
 * @date 2020/4/5 22:34
 */
public class ValueConvertUtils {


    /**
     * 常用的时间格式.
     * "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "yyyy-MM-dd HH:mm", "yyyy/MM/dd",
     * "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd'T'HH:mm:ssZZ", "yyyy-MM-ddZZ", "'T'HH:mm:ss", "'T'HH:mm:ssZZ", "HH:mm:ssZZ", "EEE, dd MMM yyyy HH:mm:ss Z"
     */
    private static final String[] PARSE_PATTERNS = {"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss.SSS"};

    public static Object convert(Object value, Type type, CustomConvent<?>... convent) throws ConventException {
        if (value == null) {
            return null;
        }
        if (!String.class.equals(type) && type instanceof Class) {
            if (((Class<?>) type).isPrimitive()) {
                PrimitiveType primitiveType = PrimitiveType.getType(type);
                if (primitiveType != null) {
                    type = primitiveType.getClassZ();
                }
            }
            if (value.getClass().equals(type)) {
                return value;
            } else if (convent != null && convent.length != 0) {
                for (CustomConvent<?> customConvent : convent) {
                    Class<?> customType = firstParameterSmart(customConvent);
                    if (Objects.equals(customType, type)) {
                        return customConvent.convent(value);
                    }

                }
            }
            if (Boolean.class.equals(type)) {
                return "yes".equalsIgnoreCase(value.toString()) || "on".equalsIgnoreCase(value.toString()) || "true".equalsIgnoreCase(value.toString()) || "1".equalsIgnoreCase(value.toString());
            } else if (Date.class.equals(type)) {
                if (value instanceof java.sql.Date || value instanceof java.sql.Timestamp) {
                    return value;
                } else if (value instanceof Number) {
                    return new Date(((Number) value).longValue());
                } else if (value instanceof String) {
                    try {
                        return new Date(Long.parseLong(value.toString().split("\\.")[0]));
                    } catch (Exception e) {
                        try {
                            return DateUtils.parseDate(value.toString(), PARSE_PATTERNS);
                        } catch (ParseException ex) {
                            throw new ConventException(ex);
                        }
                    }
                }
            }
            try {
                return ((Class<?>) type).getConstructor(String.class).newInstance(value.toString());
            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
                try {
                    return ((Class<?>) type).getConstructor(String.class).newInstance(value.toString().split("\\.")[0]);
                } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e2) {
                    throw new ConventException(e);
                }
            }
        }

        return String.class.equals(type) ? value.toString() : value;
    }


    public static <T> void setPropertyValue(String columnName, Class<T> classZ, T t, Object value, CustomConvent<?>... convent) {
        try {
            ClassMethodDto classMethod = ClassLoaderUtils.getClassMethod(classZ, columnName);
            if (classMethod != null) {
                Method writeMethod = classMethod.getWriteMethod();
                writeMethod.invoke(t, ValueConvertUtils.convert(value, writeMethod.getGenericParameterTypes()[0], convent));
            }
        } catch (Exception ignored) {
        }
    }

    private static Class<?> firstParameterSmart(CustomConvent<?> customConvent) {
        String functionClassName = customConvent.getClass().getName();
        int lambdaMarkerIndex = functionClassName.indexOf("$$Lambda$");
        if (lambdaMarkerIndex == -1) {
            return firstParameter(customConvent);
        }

        String declaringClassName = functionClassName.substring(0, lambdaMarkerIndex);
        Class<?> declaringClass;
        try {
            declaringClass = Class.forName(declaringClassName);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException("Unable to find lambda's parent class " + declaringClassName);
        }

        for (Method method : declaringClass.getDeclaredMethods()) {
            if (method.getName().startsWith("lambda$")) {
                return method.getReturnType();
            }
        }

        throw new IllegalStateException("Unable to find lambda's implementation method");
    }

    private static Class<?> firstParameter(CustomConvent<?> customConvent) {
        Class<?> customType = null;
        Type superclass = customConvent.getClass().getGenericSuperclass();
        if (superclass instanceof ParameterizedType) {
            customType = (Class<?>) ((ParameterizedType) superclass).getActualTypeArguments()[0];
        } else {
            for (Type genericInterface : customConvent.getClass().getGenericInterfaces()) {
                if (genericInterface instanceof ParameterizedType) {
                    customType = (Class<?>) ((ParameterizedType) genericInterface).getActualTypeArguments()[0];
                }
            }
        }
        return customType;
    }

    public static String serialiable(Object obj) {
        String serStr = null;
        try {
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(obj);
            serStr = byteArrayOutputStream.toString(StandardCharsets.ISO_8859_1.name());
            serStr = java.net.URLEncoder.encode(serStr, StandardCharsets.UTF_8.name());
            objectOutputStream.close();
            byteArrayOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return serStr;
    }

    public static Object deserialization(String serStr) {
        Object newObj = null;
        try {
            String redStr = java.net.URLDecoder.decode(serStr, StandardCharsets.UTF_8.name());
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(redStr.getBytes(StandardCharsets.ISO_8859_1));
            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            newObj = objectInputStream.readObject();
            objectInputStream.close();
            byteArrayInputStream.close();
        } catch (ClassNotFoundException | IOException e) {
            e.printStackTrace();
        }

        return newObj;
    }


}
